module et {
    let metaDataMap = new Map<any, [Map<string, string>, Map<string, string>]>();
    let noSerializeMetaDataMap = new Map<any, Set<string>>();
    let classMap = new Map<string, any>();
    SetClassMap(Set.name, Set.prototype);
    SetClassMap(Map.name, Map.prototype);
    SetClassMap(Long.name, Long.prototype);
    export function SetClassMap(name: string, target: any) {
        if (!classMap.has(name)) {
            classMap.set(name, target);
            let metaDataArr = [new Map<string, string>(), new Map<string, string>()];
            let noSerialMetaDataSet = new Set<string>();
            SetMetaData(target, metaDataArr as any);
            SetNoSerialMetaData(target, noSerialMetaDataSet);
            metaDataMap.set(target, metaDataArr as any);
            noSerializeMetaDataMap.set(target, noSerialMetaDataSet);
        }
    }
    function SetMetaData(target: any, metaDataArr: [Map<string, string>, Map<string, string>]) {
        let superProto = Object.getPrototypeOf(target);
        let supermetaDataArr = metaDataMap.get(target);
        if (supermetaDataArr) {
            for (const kv of supermetaDataArr[0]) {
                metaDataArr[0].set(kv[0], kv[1]);
            }
            for (const kv of supermetaDataArr[1]) {
                metaDataArr[1].set(kv[0], kv[1]);
            }
        }
        if (superProto != Object.prototype) {
            SetMetaData(superProto, metaDataArr);
        }
    }
    function SetNoSerialMetaData(target: any, noSerialMetaDataSet: Set<string>) {
        let superProto = Object.getPrototypeOf(target);
        let superNoSerialMetaDataSet = noSerializeMetaDataMap.get(target);
        if (superNoSerialMetaDataSet) {
            for (const property of superNoSerialMetaDataSet) {
                noSerialMetaDataSet.add(property);
            }
        }
        if (superProto != Object.prototype) {
            SetNoSerialMetaData(superProto, noSerialMetaDataSet);
        }
    }
    export function HasClassMap(target: any) {
        return classMap.has(target.constructor.name);
    }
    export function SerilizeClass() {
        return function (target: any) {
            SetClassMap(target.name, target.prototype);
        }
    }
    export function Serialize(descName?: string) {
        return function (target: any, attr: any) {
            DefineMetaData(target, attr, descName);
        }
    }
    export function NoSerialize() {
        return function (target: any, attr: any) {
            DefineNoSerializeMetaData(target, attr);
        }
    }
    export function DefineMetaData(target: any, property: string, descName?: string) {
        SetClassMap(target.constructor.name, target);
        let map = metaDataMap.get(target);
        if (!map) {
            map = [new Map<string, string>(), new Map<string, string>()];
            metaDataMap.set(target, map);
        }
        if (descName) {
            map[0].set(property, descName);
            map[1].set(descName, property);
        }
        else {
            map[0].set(property, property);
            map[1].set(property, property);
        }
    }
    export function DefineNoSerializeMetaData(target: any, property: string) {
        SetClassMap(target.constructor.name, target);
        let set = noSerializeMetaDataMap.get(target);
        if (!set) {
            set = new Set<string>();
            noSerializeMetaDataMap.set(target, set);
        }
        set.add(property)
    }
    export function GetMetaData(target: any, property: string) {
        let map = metaDataMap.get(target);
        if (!map) {
            return undefined;
        }
        let metadata = map[0].get(property);
        if (!metadata) {
            metadata = map[1].get(property);
        }
        return metadata;
    }
    export function IsNoSerialize(target: any, attr: string) {
        let set = noSerializeMetaDataMap.get(target);
        return set.has(attr);
    }
    export function ToJson(serializeObj: any) {
        if (serializeObj)
            return JSON.stringify(ToJsonObject(serializeObj));
        else
            return "";
    }
    export function FromJson<T>(jsonStr: string) {
        if (jsonStr)
            return FromJsonObject(JSON.parse(jsonStr)) as T;
        else
            return undefined as T;
    }
    function ToJsonObject(serializeObj: any) {
        let obj = {} as any;
        if (serializeObj instanceof Set) {
            obj["__type__"] = Set.name;
            obj["Array"] = [];
            let arr = obj["Array"];
            let values = Array.from(serializeObj);
            for (let index = 0; index < values.length; index++) {
                if (values[index] instanceof Object) {
                    arr[index] = ToJsonObject(values[index]);
                } else {
                    arr[index] = values[index];
                }
            }
        }
        else if (serializeObj instanceof Map) {
            obj["__type__"] = Map.name;
            obj["Array"] = [];
            let arr = obj["Array"];
            let kvs = Array.from(serializeObj);
            for (let index = 0; index < kvs.length; index++) {
                if (kvs[index][1] instanceof Object) {
                    arr[index] = [kvs[index][0], ToJsonObject(kvs[index][1])];
                } else {
                    arr[index] = [kvs[index][0], kvs[index][1]];
                }
            }
        }
        else if (serializeObj instanceof Array) {
            obj = [];
            for (let index = 0; index < serializeObj.length; index++) {
                if (serializeObj[index] instanceof Object) {
                    obj[index] = ToJsonObject(serializeObj[index]);
                } else {
                    obj[index] = serializeObj[index];
                }
            }
        }
        else {
            let propto = Object.getPrototypeOf(serializeObj);
            if (propto && HasClassMap(propto)) {
                obj["__type__"] = propto.constructor.name;
                Object.keys(serializeObj).forEach(property => {
                    if (IsNoSerialize(propto, property)) return;
                    const serialize = GetMetaData(propto, property);
                    if (serialize) {
                        if (serializeObj[property] instanceof Object) {
                            obj[serialize] = ToJsonObject(serializeObj[property]);
                        } else {
                            obj[serialize] = serializeObj[property];
                        }
                    }
                    else {
                        if (serializeObj[property] instanceof Object) {
                            obj[property] = ToJsonObject(serializeObj[property]);
                        } else {
                            obj[property] = serializeObj[property];
                        }
                    }
                });
            }
            else {
                Object.keys(serializeObj).forEach(property => {
                    if (serializeObj[property] instanceof Object) {
                        obj[property] = ToJsonObject(serializeObj[property]);
                    } else {
                        obj[property] = serializeObj[property];
                    }
                });
            }

        }
        return obj;
    }
    function FromJsonObject(json: any) {
        if (json) {
            if (json["__type__"]) {
                Object.setPrototypeOf(json, classMap.get(json["__type__"]));
                delete json["__type__"];
            }
            if (json instanceof Array) {
                for (const obj of json) {
                    if (obj instanceof Object) {
                        FromJsonObject(obj);
                    }
                }
            }
            else if (json instanceof Set) {
                let arr = (<any>json)["Array"];
                json = new Set(arr);
                for (const obj of arr) {
                    if (obj instanceof Object) {
                        FromJsonObject(obj);
                    }
                }
                delete json["Array"];
            }
            else if (json instanceof Map) {
                let arr = (<any>json)["Array"];
                json = new Map(arr);
                for (const obj of arr) {
                    if (obj[1] instanceof Object) {
                        FromJsonObject(obj[1]);
                    }
                }
                delete json["Array"];
            }
            else {
                let propto = Object.getPrototypeOf(json);
                if (propto) {
                    let obj = new propto.constructor();
                    Object.keys(json).forEach((property) => {
                        const serialize = GetMetaData(propto, property);
                        if (serialize) {
                            if (json[property] instanceof Object) {
                                json[serialize] = FromJsonObject(json[property]);
                            } else {
                                json[serialize] = json[property];
                            }
                            if (serialize != property) {
                                delete json[property];
                            }
                        }
                        else {
                            if (json[property] instanceof Object) {
                                json[property]=FromJsonObject(json[property]);
                            }
                        }
                    });
                    Object.keys(obj).forEach((property) => {
                        if (!json[property]) {
                            json[property] = obj[property];
                        }
                    });
                }
                else {
                    Object.keys(json).forEach((property) => {
                        if (json[property] instanceof Object) {
                            json[property]=FromJsonObject(json[property]);
                        }
                    });
                }

            }
        }
        return json;
    }
}